11 — 材质
介绍
材质用来给几何体的每个可见像素上色。
决定每个像素颜色的算法,是用一种叫 shader 的程序编写的。编写 shader 是 WebGL 和 Three.js 中最具挑战性的部分之一,但别担心;Three.js 有许多自带现成 shader 的内置材质。
我们会在以后的课程中学习如何创建自己的 shader。现在,先使用 Three.js 的材质吧。
准备工作
starter 里没有任何物体,你应该会得到一个黑色渲染。这是复习创建 Mesh 基础的好机会。
布置我们的场景
为了测试材质,我们应该布置一个可爱的场景,并加载一些纹理。
创建 3 个由不同几何体(一个球体、一个平面和一个圆环)组成的 Mesh,并在这 3 个上都使用同一个 MeshBasicMaterial。是的,你可以在多个 mesh 上共用一个材质。把球体移到左边、圆环移到右边,把它们分开。
add(...) 方法支持一次添加多个物体:
/**
* Objects
*/
// MeshBasicMaterial
const material = new THREE.MeshBasicMaterial()
const sphere = new THREE.Mesh(
new THREE.SphereGeometry(0.5, 16, 16),
material
)
sphere.position.x = - 1.5
const plane = new THREE.Mesh(
new THREE.PlaneGeometry(1, 1),
material
)
const torus = new THREE.Mesh(
new THREE.TorusGeometry(0.3, 0.2, 16, 32),
material
)
torus.position.x = 1.5
scene.add(sphere, plane, torus)
现在我们可以在 tick 函数里旋转物体,就像我们在动画课程里做的那样:
/**
* Animate
*/
const clock = new THREE.Clock()
const tick = () =>
{
const elapsedTime = clock.getElapsedTime()
// Update objects
sphere.rotation.y = 0.1 * elapsedTime
plane.rotation.y = 0.1 * elapsedTime
torus.rotation.y = 0.1 * elapsedTime
sphere.rotation.x = 0.15 * elapsedTime
plane.rotation.x = 0.15 * elapsedTime
torus.rotation.x = 0.15 * elapsedTime
// ...
}
tick()
你应该会看到你的 3 个物体缓慢旋转。
我们把 rotation.x 用 - 0.15 * elapsedTime,这样它们旋转的方向就反过来了,而且我们也能在平面消失前欣赏它更久一些:
sphere.rotation.x = - 0.15 * elapsedTime
plane.rotation.x = - 0.15 * elapsedTime
torus.rotation.x = - 0.15 * elapsedTime
我们要认识的材质会以很多不同方式使用纹理。让我们像在Textures课程里那样,用 TextureLoader 加载一些纹理。
所有纹理图片都在 static/textures/ 文件夹里。目前,我们会加载 static/textures/door/ 文件夹里的所有门纹理、static/textures/matcaps/ 文件夹里的第一个 matcap 纹理,以及 static/textures/gradients/ 文件夹里的第一个渐变纹理。
确保在实例化 material 之前做这些:
/**
* Textures
*/
const textureLoader = new THREE.TextureLoader()
const doorColorTexture = textureLoader.load('./textures/door/color.jpg')
const doorAlphaTexture = textureLoader.load('./textures/door/alpha.jpg')
const doorAmbientOcclusionTexture = textureLoader.load('./textures/door/ambientOcclusion.jpg')
const doorHeightTexture = textureLoader.load('./textures/door/height.jpg')
const doorNormalTexture = textureLoader.load('./textures/door/normal.jpg')
const doorMetalnessTexture = textureLoader.load('./textures/door/metalness.jpg')
const doorRoughnessTexture = textureLoader.load('./textures/door/roughness.jpg')
const matcapTexture = textureLoader.load('./textures/matcaps/1.png')
const gradientTexture = textureLoader.load('./textures/gradients/3.jpg') 就像我们之前看到的,用作 map 和 matcap 的纹理应该用 sRGB 编码,我们需要通过把这些纹理的 colorSpace 设为 THREE.sRGBColorSpace 来告知 Three.js:
doorColorTexture.colorSpace = THREE.SRGBColorSpace
matcapTexture.colorSpace = THREE.SRGBColorSpace 为了确保所有纹理都加载好,你可以像 Textures 课程里我们看到的那样,用 map 属性把纹理用在材质上:
const material = new THREE.MeshBasicMaterial({ map: doorColorTexture })
到目前为止,我们只用了 MeshBasicMaterial,它会给我们几何体应用统一的颜色或纹理。
如果你在 Three.js 文档搜索"material",你会看到有很多类可以让我们用。让我们一个一个试试。
MeshBasicMaterial
MeshBasicMaterial 大概是最"基础"的材质了……但还有几个我们还没覆盖的属性。
你可以在实例化材质时,在作为参数传入的对象里设置这些属性中的大部分,但也可以直接在实例上修改这些属性:
const material = new THREE.MeshBasicMaterial({
map: doorColorTexture
})
// Equivalent
const material = new THREE.MeshBasicMaterial()
material.map = doorColorTexture 课程剩下的部分我们会使用第二种方法,但你怎么喜欢怎么来。
Map
map 属性会把一个纹理应用到几何体表面:
material.map = doorColorTexture
Color
color 属性会给几何体表面应用一个统一的颜色。当你直接修改 color 属性时,你必须实例化一个 Color 类。你可以使用很多不同的格式。
先试试注释掉 map,测试这些颜色格式:
// material.map = doorColorTexture
material.color = new THREE.Color('#ff0000')
material.color = new THREE.Color('#f00')
material.color = new THREE.Color('red')
material.color = new THREE.Color('rgb(255, 0, 0)')
material.color = new THREE.Color(0xff0000)
把 color 和 map 组合起来会用颜色给纹理着色:
material.map = doorColorTexture
material.color = new THREE.Color('#ff0000')
Wireframe
wireframe 属性会以 1px 细线显示组成你几何体的三角形,无论相机距离多远:
// material.map = doorColorTexture
// material.color = new THREE.Color('#ff0000')
material.wireframe = true
如你所见,平面只由两个三角形组成,这在本课后面会变成一个问题。
Opacity
opacity 属性控制透明度,但要起作用,你需要把 transparent 属性设为 true,以告知 Three.js 这个材质现在支持透明:
// material.map = doorColorTexture
// material.color = new THREE.Color('#ff0000')
// material.wireframe = true
material.transparent = true
material.opacity = 0.5
虽然它看起来是灰的,但那不是因为我们能透过白色材质看到后面的黑色背景。
AlphaMap
既然透明已经能用了,我们就可以用 alphaMap 属性来用纹理控制透明:
// material.map = doorColorTexture
// material.color = new THREE.Color('#ff0000')
// material.wireframe = true
material.transparent = true
// material.opacity = 0.5
material.alphaMap = doorAlphaTexture
Side
side 属性让你决定面的哪一侧可见。默认情况下正面可见(THREE.FrontSide),但你可以改为显示背面(THREE.BackSide)或两面都显示(THREE.DoubleSide):
// material.map = doorColorTexture
// material.color = new THREE.Color('#ff0000')
// material.wireframe = true
// material.transparent = true
// material.opacity = 0.5
// material.alphaMap = doorAlphaTexture
material.side = THREE.DoubleSide
你应该能看到平面的正面和背面。
尽可能避免使用 THREE.DoubleSide,因为它实际渲染时需要更多资源,哪怕那一侧并不可见。
这些属性中的一些,比如 wireframe 或 opacity,可以用于下面的大多数材质。我们不会每次都重复它们。
MeshNormalMaterial
MeshNormalMaterial 会显示一种漂亮的紫色、偏蓝的颜色,看起来就像我们在Textures课程里看到的法线纹理。这不是巧合,因为两者都与我们所说的"normals"(法线)有关。
我们也把它叫做 material,以确保它替换所有物体上的材质,并把 MeshBasicMaterial 注释掉,以防你以后想用回来:
// // MeshBasicMaterial
// const material = new THREE.MeshBasicMaterial()
// material.map = doorColorTexture
// material.color = new THREE.Color('#ff0000')
// material.wireframe = true
// material.transparent = true
// material.opacity = 0.5
// material.alphaMap = doorAlphaTexture
// material.side = THREE.DoubleSide
// MeshNormalMaterial
const material = new THREE.MeshNormalMaterial()
法线是编码在每个顶点里的信息,包含面朝外的方向。如果你把这些法线显示成箭头,你会得到从组成几何体的每个顶点上伸出的直线。
法线可以用于很多事情,比如计算如何照亮这个面,或者环境应该如何在几何体表面反射或折射。
使用 MeshNormalMaterial 时,颜色只是显示法线相对于相机的朝向。如果你绕着球体旋转,你会发现颜色始终一样,无论你在看球体的哪一部分。
虽然你可以使用我们从 MeshBasicMaterial 中认识的某些属性,比如 wireframe、transparent、opacity 和 side,但还有一个你可以使用的新属性,叫做 flatShading:
material.flatShading = true
flatShading 会把面压平,意思是法线不会在顶点之间插值。
MeshNormalMaterial 对调试法线很有用,但它看起来也很棒,你可以原样使用它,就像 ilithya 在她的作品集网站上做的那样 https://www.ilithya.rocks。
MeshMatcapMaterial
MeshMatcapMaterial 是一个奇妙的材质,因为它看起来可以非常好,同时仍然非常高效。
要让它工作,MeshMatcapMaterial 需要一个看起来像球体的参考纹理。
然后,材质会根据法线相对于相机的朝向,从纹理中选取颜色。
注释掉之前的材质,实例化一个 MeshMatcapMaterial,并把它的 matcap 属性设为 matcapTexture:
// // MeshNormalMaterial
// const material = new THREE.MeshNormalMaterial()
// material.flatShading = true
// MeshMatcapMaterial
const material = new THREE.MeshMatcapMaterial()
material.matcap = matcapTexture
这些 mesh 看起来像被照亮了,但那是纹理制造出来的假象。场景里并没有灯光。
唯一的问题是,无论相机朝向如何,结果都一样。而且你无法更新灯光,因为根本没有灯光。
试试 /static/textures/matcaps/ 文件夹里其他可用的纹理(只保留下面其中一行):
const matcapTexture = textureLoader.load('/textures/matcaps/2.png')
const matcapTexture = textureLoader.load('/textures/matcaps/3.png')
const matcapTexture = textureLoader.load('/textures/matcaps/4.png')
const matcapTexture = textureLoader.load('/textures/matcaps/5.png')
const matcapTexture = textureLoader.load('/textures/matcaps/6.png')
const matcapTexture = textureLoader.load('/textures/matcaps/7.png')
const matcapTexture = textureLoader.load('/textures/matcaps/8.png')
如果你想知道去哪里找 matcap 纹理,那就看看下面这份海量 matcap 列表:https://github.com/nidorx/matcaps
注意,授权并非全部经过验证,除了个人项目之外,你也许不被允许使用它们。
你也可以在 3D 软件里,通过在相机前渲染一个正方形图片中的球体来创建自己的 matcap。你甚至可以尝试在 Photoshop 这样的 2D 软件里创建 matcap。最后,你还可以使用像 Kevin Chapelier 构建的在线工具:https://www.kchapelier.com/matcap-studio/:
MeshDepthMaterial
MeshDepthMaterial 会简单地把几何体涂成白色,如果它靠近相机的 near 值;涂成黑色,如果它靠近相机的 far 值:
// // MeshMatcapMaterial
// const material = new THREE.MeshMatcapMaterial()
// material.matcap = matcapTexture
// MeshDepthMaterial
const material = new THREE.MeshDepthMaterial()
实际上它比这更复杂一些,这个材质实际上用于把深度保存到纹理中,可供以后复杂的计算使用,比如处理阴影。
我们会在以后的课程里更多了解它。
MeshLambertMaterial
下一个材质是 MeshLambertMaterial:
// // MeshDepthMaterial
// const material = new THREE.MeshDepthMaterial()
// MeshLambertMaterial
const material = new THREE.MeshLambertMaterial()
刚刚发生了什么?屏幕现在完全变黑了,虽然控制台里没有报错。
事情是这样的:MeshLambertMaterial 是这个列表里第一个需要灯光才能被看见的材质。
好吧,让我们加一些灯光。
添加几个灯光
我们要给场景加两个简单的灯光,但不会深入这个主题,因为有整整一节课是讲灯光的。
创建一个 AmbientLight(环境光)并添加进场景:
/**
* Lights
*/
const ambientLight = new THREE.AmbientLight(0xffffff, 1)
scene.add(ambientLight) 创建一个 PointLight(点光源)并添加进场景:
// ...
const pointLight = new THREE.PointLight(0xffffff, 30)
pointLight.position.x = 2
pointLight.position.y = 3
pointLight.position.z = 4
scene.add(pointLight)
事情开始变得真实了,如你所见。虽然光照效果还不那么令人信服,但这是一个好的开始。
MeshLambertMaterial 支持 MeshBasicMaterial 的相同属性,另外还有一些与灯光相关的属性。我们稍后会在课程里用更合适的材质来覆盖这些属性。
MeshLambertMaterial 是使用灯光时性能最好的材质。遗憾的是,它的参数不太理想,如果你仔细观察球体这样带圆角的几何体,你会看到奇怪的花纹。
MeshPhongMaterial
MeshPhongMaterial 和 MeshLambertMaterial 非常相似,但奇怪的花纹没那么明显,而且你还能看到光在几何体表面的反射:
// // MeshLambertMaterial
// const material = new THREE.MeshLambertMaterial()
// MeshPhongMaterial
const material = new THREE.MeshPhongMaterial()
MeshPhongMaterial 的性能不如 MeshLambertMaterial。不过,在这个层面上真没什么关系。
你可以用 shininess 属性控制光的反射。值越高,表面越闪亮。你也可以用 specular 属性改变反射的颜色:
material.shininess = 100
material.specular = new THREE.Color(0x1188ff)
MeshToonMaterial
MeshToonMaterial 在属性上类似于 MeshLambertMaterial,但风格是卡通式的:
// // MeshPhongMaterial
// const material = new THREE.MeshPhongMaterial()
// material.shininess = 100
// material.specular = new THREE.Color(0x1188ff)
// MeshToonMaterial
const material = new THREE.MeshToonMaterial()
默认情况下,你只能得到两段式着色(一段是阴影,一段是高光)。要给着色增加更多层级,你可以把课程开头加载的 gradientTexture 用在 gradientMap 属性上:
material.gradientMap = gradientTexture
卡通效果不生效了。这是因为渐变纹理其实是一张非常非常小的 3x1 像素纹理。当从这个纹理提取像素时,GPU 会把它们混合。
不是这样的效果:
而是这样的效果:
幸运的是,多亏了 minFilter、magFilter,我们可以控制 GPU 如何处理这种纹理,就像我们在Textures课程里谈论 mipmapping 时看到的。
把 minFilter 和 magFilter 改成 THREE.NearestFilter。
// MeshToonMaterial
const material = new THREE.MeshToonMaterial()
gradientTexture.minFilter = THREE.NearestFilter
gradientTexture.magFilter = THREE.NearestFilter
material.gradientMap = gradientTexture
你现在应该能看到带中间层级的卡通效果了。
而且因为 THREE.NearestFilter 实际不使用纹理的任何 mipmap 版本,我们可以通过把 gradientTexture.generateMipmaps 设为 false 来停用 mipmap 的生成,从而释放一些内存:
gradientTexture.minFilter = THREE.NearestFilter
gradientTexture.magFilter = THREE.NearestFilter
gradientTexture.generateMipmaps = false 你可以用位于 static/textures/gradients.5.jpg 的图片尝试更多层级:
const gradientTexture = textureLoader.load('/textures/gradients/5.jpg')
MeshStandardMaterial
MeshStandardMaterial 使用基于物理的渲染原则。是的,我们说的是在Textures课程里看到的 PBR。像 MeshLambertMaterial 和 MeshPhongMaterial 一样,它支持灯光,但使用了更真实的算法和更好的参数,比如 roughness 和 metalness。
它被称为"standard"(标准),是因为 PBR 已经成为许多软件、引擎和库的标准。思路是用真实的参数获得真实的结果,而且无论你使用什么技术,你应该得到非常相似的结果:
// // MeshToonMaterial
// const material = new THREE.MeshToonMaterial()
// gradientTexture.minFilter = THREE.NearestFilter
// gradientTexture.magFilter = THREE.NearestFilter
// gradientTexture.generateMipmaps = false
// material.gradientMap = gradientTexture
// MeshStandardMaterial
const material = new THREE.MeshStandardMaterial()
你可以直接修改 roughness 和 metalness 属性:
material.metalness = 0.45
material.roughness = 0.65
添加一个调试 UI
现在正是添加调试 UI 的好时机。这会非常有助于测试不同的属性。
首先,我们必须给项目添加 lil-gui 依赖。在终端里,在项目文件夹中(服务器当前应该正在运行),使用以下命令:
npm install lil-gui 然后,在你的代码顶部导入 lil-gui(如果你停掉了服务器,别忘了用 npm run dev 重新启动):
import GUI from 'lil-gui' 现在你可以创建一个 GUI 的实例了:
/**
* Debug
*/
const gui = new GUI() 然后,添加调整项(在创建材质之后):
const material = new THREE.MeshStandardMaterial()
material.metalness = 0.45
material.roughness = 0.65
gui.add(material, 'metalness').min(0).max(1).step(0.0001)
gui.add(material, 'roughness').min(0).max(1).step(0.0001)
就是这样。现在你可以随意修改 metalness 和 roughness 了。
添加环境贴图
我们对 MeshStandardMaterial 还没有结束,但继续之前,我们要添加一个附加功能,叫做环境贴图(environment map)。
环境贴图就像是场景周围环境的一张图像,你可以在 /static/textures/environmentMap/2k.hdr 找到它。如果你的操作系统支持,你也许能预览它。如果不行,别担心,这不重要,Three.js 能处理它。
除了当前的 DirectionalLight 和 AmbientLight 之外,你可以用它给物体添加反射、折射,还有照明。
就像灯光一样,我们不会详细展开它,因为有一节专门的课,你还会在里面学到很多创建自己环境贴图的方法。
让我们先改变 MeshStandardMaterial 的 metalness 和 roughness,以便更好地欣赏即将到来的环境贴图:
const material = new THREE.MeshStandardMaterial()
material.metalness = 0.7
material.roughness = 0.2
要加载前面提到的环境贴图文件,我们需要使用 RGBELoader。从 three/examples/jsm/loaders/RGBELoader.js 导入 RGBELoader:
import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader.js' 接下来,我们需要把它实例化为 rgbeLoader,并用它的 load() 方法加载 ./textures/environmentMap/2k.hdr 文件:
/**
* Environment map
*/
const rgbeLoader = new RGBELoader()
rgbeLoader.load('./textures/environmentMap/2k.hdr', (environmentMap) =>
{
console.log(environmentMap)
})
与 textureLoader 不同,我们需要把回调函数作为第二个参数传进去。我们可以把加载好的环境贴图作为该函数的参数取出来。
要把它应用到场景,我们需要把它 mapping 属性改为 THREE.EquirectangularReflectionMapping,然后把它赋值给 scene 的 background 和 environment 属性:
rgbeLoader.load('./textures/environmentMap/2k.hdr', (environmentMap) =>
{
environmentMap.mapping = THREE.EquirectangularReflectionMapping
scene.background = environmentMap
scene.environment = environmentMap
})
你应该能看到环境在几何体表面反射。试着调整 metalness 和 roughness 获得不同的结果。
虽然我们只是现在才添加环境贴图,但它也兼容 MeshLambertMaterial 和 MeshPhongMaterial。
而且既然环境贴图本身已经足够,我们要移除或注释掉 AmbientLight 和 PointLight:
// const ambientLight = new THREE.AmbientLight(0xffffff, 1)
// scene.add(ambientLight)
// const pointLight = new THREE.PointLight(0xffffff, 30)
// pointLight.position.x = 2
// pointLight.position.y = 3
// pointLight.position.z = 4
// scene.add(pointLight)
更多属性
让我们继续看 MeshStandardMaterial 的其他属性。
map 属性允许你应用一个简单的纹理。你可以用 doorColorTexture:
material.map = doorColorTexture
aoMap 属性(字面意思是"ambient occlusion map"环境光遮蔽贴图)会在纹理较暗的地方添加阴影。
然后,用 doorAmbientOcclusionTexture 纹理添加 aoMap,并用 aoMapIntensity 属性控制强度:
material.aoMap = doorAmbientOcclusionTexture
material.aoMapIntensity = 1
缝隙应该看起来更暗,这制造了对比度并增加了立体感。
注意,aoMap 只影响由 AmbientLight、环境贴图,以及你会在以后的课程中看到的 HemisphereLight 产生的光。
displacementMap 属性会移动顶点来创造真实的起伏:
material.displacementMap = doorHeightTexture
它看起来应该很糟糕。那是由于我们几何体上顶点不足,而且位移过强。
给几何体添加更多细分:
new THREE.SphereGeometry(0.5, 64, 64),
// ...
new THREE.PlaneGeometry(1, 1, 100, 100),
// ...
new THREE.TorusGeometry(0.3, 0.2, 64, 128),
起伏看起来更精细了,但还是太强。我们可以用 displacementScale 属性控制它:
material.displacementScale = 0.1
与其为整个几何体指定统一的 metalness 和 roughness,我们可以使用 metalnessMap 和 roughnessMap:
material.metalnessMap = doorMetalnessTexture
material.roughnessMap = doorRoughnessTexture
反射看起来很奇怪,因为 metalness 和 roughness 属性仍然影响着 metalnessMap 和 roughnessMap。要让它们正常工作,我们需要把 metalness 和 roughness 都设为 1:
material.metalness = 1
material.roughness = 1
现在我们能欣赏到门上金属部分的反射了。注意,这个纹理被设计得看起来像木头表面有一层清漆,所以我们仍然能感知到一些反射。
normalMap 会假装法线的朝向,无论细分多少都给表面添加细节:
material.normalMap = doorNormalTexture
你可以用 normalScale 属性改变法线的强度。小心,它是一个 Vector2:
material.normalScale.set(0.5, 0.5)
最后,你可以用 alphaMap 属性控制透明度。别忘了把 transparent 属性设为 true:
material.transparent = true
material.alphaMap = doorAlphaTexture
MeshPhysicalMaterial
MeshPhysicalMaterial 和 MeshStandardMaterial 相同,但额外支持一些效果,比如 clearcoat(清漆层)、sheen(绒面光泽)、iridescence(虹彩)和 transmission(透射)。
要实现这个材质,复制整个 MeshStandardMaterial、它的属性和它的调整项,然后把类换成 MeshPhysicalMaterial。别忘了注释掉 MeshStandardMaterial:
/**
* MeshPhysicalMaterial
*/
// Base material
const material = new THREE.MeshPhysicalMaterial()
material.metalness = 1
material.roughness = 1
material.map = doorColorTexture
material.aoMap = doorAmbientOcclusionTexture
material.aoMapIntensity = 1
material.displacementMap = doorHeightTexture
material.displacementScale = 0.1
material.metalnessMap = doorMetalnessTexture
material.roughnessMap = doorRoughnessTexture
material.normalMap = doorNormalTexture
material.normalScale.set(0.5, 0.5)
gui.add(material, 'metalness').min(0).max(1).step(0.0001)
gui.add(material, 'roughness').min(0).max(1).step(0.0001)
之前的所有属性都受支持,因为 MeshPhysicalMaterial 继承自 MeshStandardMaterial,现在我们能访问这些新功能了。我们会在保留这个基础材质的同时,单独测试它们。
Clearcoat
clearcoat 会在实际材质之上模拟一层薄薄的清漆。这层清漆有自己独立的反射属性,同时我们仍然能看到它后面默认材质的样子。
这里有一个示例。
添加 clearcoat 和 clearcoatRoughness 属性及对应的调整项:
// Clearcoat
material.clearcoat = 1
material.clearcoatRoughness = 0
gui.add(material, 'clearcoat').min(0).max(1).step(0.0001)
gui.add(material, 'clearcoatRoughness').min(0).max(1).step(0.0001)
感觉就像同一个材质上盖着一层玻璃。
Sheen
sheen(绒面光泽)会在从狭窄角度观察材质时让它高亮。我们通常能在织物这类毛绒材质上看到这种效果。
这里有一个示例。
注释掉 clearcoat 那部分。
添加 sheen、sheenRoughness 和 sheenColor 属性及各自对应的调整项:
// Sheen
material.sheen = 1
material.sheenRoughness = 0.25
material.sheenColor.set(1, 1, 1)
gui.add(material, 'sheen').min(0).max(1).step(0.0001)
gui.add(material, 'sheenRoughness').min(0).max(1).step(0.0001)
gui.addColor(material, 'sheenColor')
同样,这种效果是在从狭窄角度观察材质时应用的。虽然它最适合织物类材质,但它让材质有一种柔和的质感。
Iridescence
iridescence(虹彩)是一种能看到彩色光斑的效果,就像油渍、肥皂泡,或者那些年纪够大的人记得的激光影碟(LaserDisc)。
这里有一个示例。
注释掉 sheen 那部分。
添加 iridescence、iridescenceIOR 和 iridescenceThicknessRange 属性及各自对应的调整项:
// Iridescence
material.iridescence = 1
material.iridescenceIOR = 1
material.iridescenceThicknessRange = [ 100, 800 ]
gui.add(material, 'iridescence').min(0).max(1).step(0.0001)
gui.add(material, 'iridescenceIOR').min(1).max(2.333).step(0.0001)
gui.add(material.iridescenceThicknessRange, '0').min(1).max(1000).step(1)
gui.add(material.iridescenceThicknessRange, '1').min(1).max(1000).step(1)
和前面两个效果一样,虹彩大多在从狭窄角度观察材质时才明显。
Transmission
transmission(透射)会让光穿过材质。它不仅仅是 opacity 那种透明度,因为物体后面的图像会被扭曲。
这里有一个示例。
注释掉 iridescence 那部分。
添加 transmission、ior 和 thickness 属性及各自对应的调整项:
// Transmission
material.transmission = 1
material.ior = 1.5
material.thickness = 0.5
gui.add(material, 'transmission').min(0).max(1).step(0.0001)
gui.add(material, 'ior').min(1).max(10).step(0.0001)
gui.add(material, 'thickness').min(0).max(1).step(0.0001)
这些物体感觉像半透明。
ior 是 Index Of Refraction(折射率)的缩写,取决于你想模拟的材质类型。
钻石的 ior 是 2.417,水的 ior 是 1.333,空气的 ior 是 1.000293。
你可以在维基百科找到完整列表 https://en.wikipedia.org/wiki/List_of_refractive_indices
thickness 是一个固定值,物体的实际厚度不会被考虑。
现在,我们有很多贴图在搞乱我们的材质,但 transmission 用在纯净材质上看起来真的很好。
移除或注释掉所有贴图,并把 metalness 和 roughness 设为 0:
const material = new THREE.MeshPhysicalMaterial()
material.metalness = 0
material.roughness = 0
// material.map = doorColorTexture
// material.aoMap = doorAmbientOcclusionTexture
// material.aoMapIntensity = 1
// material.displacementMap = doorHeightTexture
// material.displacementScale = 0.1
// material.metalnessMap = doorMetalnessTexture
// material.roughnessMap = doorRoughnessTexture
// material.normalMap = doorNormalTexture
// material.normalScale.set(0.5, 0.5)
// material.transparent = true
// material.alphaMap = doorAlphaTexture
很酷的一点是,我们仍然可以摆弄 roughness,得到一种光泽的效果:
MeshPhysicalMaterial 就到这里。注意,我们并没有检查它的每一项可能属性,但其他属性没有那么重要。
另外,请记住,MeshPhysicalMaterial 在性能方面是最差的材质。如果你把这个材质应用到覆盖屏幕大部分区域的很多物体上,别指望每个设备的帧率都能很好。
留到以后的材质
还有一些材质我们不会在本课测试,因为它们需要事先掌握更多知识,而这些知识我们会在课程后面学到。
PointsMaterial
你可以用 PointsMaterial 处理粒子、它们的大小、颜色、画的是什么等等。
ShaderMaterial 和 RawShaderMaterial
ShaderMaterial 和 RawShaderMaterial 都可以用来使用一种叫 GLSL 的特殊语言创建自己的材质。这是一个非常复杂的主题,专门有一整章来讲解。
结语
好多材质、好多属性。别担心,我们会在整门课程中反复使用它们来练习。
随意调整它们,并把它们添加到调试 UI 里。
我们还顺带接触了灯光和环境贴图这样的主题。虽然它们可能还有点模糊,但如前所述,我们会在专门的课程中深入这些主题。
最后,别忘了,你使用的材质会极大影响性能。始终留意帧率,尤其是在手机这样的弱设备上。